---
title: Next.js + Hanko
sidebar_label: Next.js
keywords: [next, nextjs]
sidebar_custom_props:
  docCardIconName: nextjs-dark
---

import Tabs from "@theme/Tabs";
import TabItem from "@theme/TabItem";

# Next.js

Learn how to quickly add authentication and user profile in your Next.js app using Hanko.

## Install @teamhanko/hanko-elements

Once you've initialized your Next app, installing hanko-elements provides you with access to the prebuilt components: `hanko-auth` and `hanko-profile`.

```shell npm2yarn
npm install @teamhanko/hanko-elements
```

## Add the Hanko API URL

Retrieve the API URL from the [Hanko console](https://cloud.hanko.io/) and place it in your .env file.

```sh title=".env.local"
NEXT_PUBLIC_HANKO_API_URL=https://f4****-4802-49ad-8e0b-3d3****ab32.hanko.io
```

## Add `<hanko-auth>` component

The `<hanko-auth>` web component adds a login interface to your app. Begin by importing the register function from `@teamhanko/hanko-elements` into your Next.js component. Call it with the Hanko API URL as an argument to register `<hanko-auth>` with the browser's [CustomElementRegistry](https://developer.mozilla.org/de/docs/Web/API/CustomElementRegistry). Once done, include it in your JSX.

```mdx-code-block
<Tabs groupId="environment">
<TabItem value="app-directory" label="App directory">
```

```jsx title="components/HankoAuth.tsx" showLineNumbers
"use client";

import { useEffect } from "react";
import { register } from "@teamhanko/hanko-elements";

const hankoApi = process.env.NEXT_PUBLIC_HANKO_API_URL;

export default function HankoAuth() {
  useEffect(() => {
    register(hankoApi).catch((error) => {
      // handle error
    });
  }, []);

  return <hanko-auth />;
}
```

```mdx-code-block
</TabItem>
<TabItem value="pages-directory" label="Pages directory">
```

```jsx title="components/HankoAuth.tsx" showLineNumbers
import { useEffect } from "react";
import { register } from "@teamhanko/hanko-elements";

const hankoApi = process.env.NEXT_PUBLIC_HANKO_API_URL;

export default function HankoAuth() {
  useEffect(() => {
    register(hankoApi).catch((error) => {
      // handle error
    });
  }, []);

  return <hanko-auth />;
}
```

```mdx-code-block
</TabItem>
</Tabs>
```


Now simply import the component you just created (HankoAuth)

```mdx-code-block
<Tabs groupId="environment">
<TabItem value="app-directory" label="App directory">
```

```jsx title="app/login/page.tsx" showLineNumbers
import HankoAuth from "@/components/HankoAuth";

export default function LoginPage() {
 return (
   <HankoAuth />
 );
}
```

```mdx-code-block
</TabItem>
<TabItem value="pages-directory" label="Pages directory">
```

```jsx title="pages/login.tsx" showLineNumbers
import dynamic from "next/dynamic";

export default function LoginPage() {
  const HankoAuth = dynamic(
    () => import('@/components/HankoAuth'),
    { ssr: false },
  )
  return (
    <Suspense fallback={"Loading ..."}>
      <HankoAuth/>
    </Suspense>
  )
}
```
:::info
**Why do we use dynamic import in the Nextjs pages directory?** 🤔

  The register function tries to set up the `<hanko-auth>` element for the browser. But, Next.js pre-renders pages before displaying them. Since it doesn't have access to the browser's `window` object during this pre-render phase, the registration of the custom element would fail.

  To fix this, we can use Next.js's [dynamic import](https://nextjs.org/docs/advanced-features/dynamic-import) to load our component only when it's displayed on the client-side and skip the pre-creation step.
:::
```mdx-code-block
</TabItem>
</Tabs>
```


## Define event callbacks

The Hanko client from `@teamhanko/hanko-elements` lets you "listen" for specific [events](https://github.com/teamhanko/hanko/blob/main/frontend/elements/README.md#events). It simplifies the process of subscribing to events, such as user logins.

```mdx-code-block
<Tabs groupId="environment">
<TabItem value="app-directory" label="App directory">
```

```jsx title="components/HankoAuth.tsx" showLineNumbers
"use client";

import { useEffect, useCallback, useState } from "react";
import { useRouter } from "next/navigation";
import { register, Hanko } from "@teamhanko/hanko-elements";

const hankoApi = process.env.NEXT_PUBLIC_HANKO_API_URL;

export default function HankoAuth() {
  const router = useRouter();

  const [hanko, setHanko] = useState<Hanko>();

  useEffect(() => {
    import("@teamhanko/hanko-elements").then(({ Hanko }) =>
      setHanko(new Hanko(hankoApi))
    );
  }, []);

  const redirectAfterLogin = useCallback(() => {
    // successfully logged in, redirect to a page in your application
    router.replace("/dashboard");
  }, [router]);

  useEffect(
    () =>
      hanko?.onAuthFlowCompleted(() => {
        redirectAfterLogin();
      }),
    [hanko, redirectAfterLogin]
  );

  useEffect(() => {
    register(hankoApi).catch((error) => {
      // handle error
    });
  }, []);

  return <hanko-auth />;
}
```

```mdx-code-block
</TabItem>
<TabItem value="pages-directory" label="Pages directory">
```

```jsx filename="components/HankoAuth.tsx" showLineNumbers
import { useEffect, useCallback, useState } from "react";
import { useRouter } from "next/router";
import { register, Hanko } from "@teamhanko/hanko-elements";

const hankoApi = process.env.NEXT_PUBLIC_HANKO_API_URL;

export default function HankoAuth() {
  const router = useRouter();

  const [hanko, setHanko] = useState<Hanko>();

  useEffect(() => {
    import("@teamhanko/hanko-elements").then(({ Hanko }) =>
      setHanko(new Hanko(hankoApi))
    );
  }, []);

  const redirectAfterLogin = useCallback(() => {
    // successfully logged in, redirect to a page in your application
    router.replace("/dashboard");
  }, [router]);

  useEffect(
    () =>
      hanko?.onAuthFlowCompleted(() => {
        redirectAfterLogin();
      }),
    [hanko, redirectAfterLogin]
  );

  useEffect(() => {
    register(hankoApi).catch((error) => {
      // handle error
    });
  }, []);

  return <hanko-auth />;
}
```
```mdx-code-block
</TabItem>
</Tabs>
```

## Add `<hanko-profile>` component {#hanko-profile}

The `<hanko-profile>` component offers an interface for managing email addresses and passkeys.

```mdx-code-block
<Tabs groupId="environment">
<TabItem value="app-directory" label="App directory">
```

```jsx title="components/HankoProfile.jsx" showLineNumbers
"use client"

import { useEffect } from "react";
import { register } from "@teamhanko/hanko-elements";

const hankoApi = process.env.NEXT_PUBLIC_HANKO_API_URL;

export default function HankoProfile() {
  useEffect(() => {
    register(hankoApi).catch((error) => {
      // handle error
    });
  }, []);

  return <hanko-profile />;
}
```

```mdx-code-block
</TabItem>
<TabItem value="pages-directory" label="Pages directory">
```

```jsx filename="components/HankoProfile.jsx" showLineNumbers
import { useEffect } from "react";
import { register } from "@teamhanko/hanko-elements";

const hankoApi = process.env.NEXT_PUBLIC_HANKO_API_URL;

export default function HankoProfile() {
  useEffect(() => {
    register(hankoApi).catch((error) => {
      // handle error
    });
  }, []);

  return <hanko-profile />;
}
```

```mdx-code-block
</TabItem>
</Tabs>
```

## Implement logout functionality

You can use `@teamhanko/hanko-elements` to easily manage user logouts. Below, we make a logout button component that you can use anywhere. 

```mdx-code-block
<Tabs groupId="environment">
<TabItem value="app-directory" label="App directory">
```

```jsx title="components/LogoutButton.tsx" showLineNumbers
"use client";

import { useState, useEffect } from "react";
import { useRouter } from "next/navigation";
import { Hanko } from "@teamhanko/hanko-elements";

const hankoApi = process.env.NEXT_PUBLIC_HANKO_API_URL;

export function LogoutBtn() {
  const router = useRouter();
  const [hanko, setHanko] = useState<Hanko>();

  useEffect(() => {
    import("@teamhanko/hanko-elements").then(({ Hanko }) =>
      setHanko(new Hanko(hankoApi ?? ""))
    );
  }, []);

  const logout = async () => {
    try {
      await hanko?.user.logout();
      router.push("/login");
      router.refresh();
      return;
    } catch (error) {
      console.error("Error during logout:", error);
    }
  };

  return <button onClick={logout}>Logout</button>;
}
```

```mdx-code-block
</TabItem>
<TabItem value="pages-directory" label="Pages directory">
```

```jsx filename="components/LogoutButton.tsx" showLineNumbers
import { useState, useEffect } from "react";
import { useRouter } from "next/router";
import { Hanko } from "@teamhanko/hanko-elements";

const hankoApi = process.env.NEXT_PUBLIC_HANKO_API_URL;

export function LogoutBtn() {
  const router = useRouter();
  const [hanko, setHanko] = useState<Hanko>();

  useEffect(() => {
    import("@teamhanko/hanko-elements").then(({ Hanko }) =>
      setHanko(new Hanko(hankoApi ?? ""))
    );
  }, []);

  const logout = async () => {
    try {
      await hanko?.user.logout();
      router.push("/login");
      router.refresh();
      return;
    } catch (error) {
      console.error("Error during logout:", error);
    }
  };

  return <button onClick={logout}>Logout</button>;
}
```

```mdx-code-block
</TabItem>
</Tabs>
```

## Customize component styles

You can customize the appearance of `hanko-auth` and `hanko-profile` components using CSS variables and parts. Refer to our [customization guide](https://github.com/teamhanko/hanko/tree/main/frontend/elements#ui-customization).

## Securing routes with Middleware

To add JWT verification middleware with Hanko in your Next.js application, we're using [jose library](https://www.npmjs.com/package/jose). However, you're free to choose any other suitable library. This middleware will ensure secure access to specific routes, like `/dashboard` here, by checking for valid JWT. Here we define the Hanko API URL, extract and verify the JWT from cookies, and redirect unauthorized users to the login page.

```jsx title="middleware.ts" showLineNumbers
import { NextResponse, NextRequest } from "next/server";

import { jwtVerify, createRemoteJWKSet } from "jose";

const hankoApiUrl = process.env.NEXT_PUBLIC_HANKO_API_URL;

export async function middleware(req: NextRequest) {
  const hanko = req.cookies.get("hanko")?.value;

  const JWKS = createRemoteJWKSet(
    new URL(`${hankoApiUrl}/.well-known/jwks.json`)
  );

  try {
    const verifiedJWT = await jwtVerify(hanko ?? "", JWKS);
  } catch {
    return NextResponse.redirect(new URL("/login", req.url));
  }
}

export const config = {
  matcher: ["/dashboard"],
};
```
